<?php
namespace App\Component\Payment;

use App\Core\Exception\DefaultException;
use App\Model\Account\CollectionAccountModel;
use EasySwoole\Component\Singleton;
use WeChatPay\Builder;
use WeChatPay\Crypto\AesGcm;
use WeChatPay\Crypto\Rsa;
use WeChatPay\Formatter;
use WeChatPay\Util\PemUtil;

class WechatpayApp
{

    use Singleton;

    private $app_id; //应用ID
    private $api_key; //在商户平台上设置的APIv3密钥
    private $partner_id; //商户ID
    private $serial_no; //证书序列号
    private $notify_url; //回调地址
    private $public_key; //公钥
    private $private_key; //应用私钥
    private $gate_way; //请求网关

    public function __construct(){

    }

    /**
     * 支付方式配置信息 paymentConfig
     * @param int $id 支付方式ID
     * @return void
     */
    private function paymentConfig(int $id, string $gate_way = null){
        $payment = CollectionAccountModel::getOne(['id' => $id, 'state' => 1, 'channel' => 1]);
        if(empty($payment->id)) {
            throw new DefaultException('支付方式错误');
        }

        $this->app_id = $payment->app_id;
        $this->partner_id = $payment->partner_id;
        $this->serial_no = $payment->serial_no;
        $this->notify_url = $payment->notify_url;
        $this->public_key = $payment->public_key;
        $this->private_key = $payment->private_key;
        $this->api_key = $payment->api_key;
//
//        if(empty($gate_way)){
//            if(\EasySwoole\EasySwoole\Config::getInstance()->getConf('ENV') == 'dev'){
//                $this->gate_way = \EasySwoole\Pay\AliPay\GateWay::SANDBOX;
//            }else{
//                $this->gate_way = \EasySwoole\Pay\AliPay\GateWay::NORMAL;
//            }
//        }else{
//            $this->gate_way = $gate_way;
//        }
    }

    /**
     * 微信支付 pay
     * @param int $payment_id 支付方式ID
     * @param string $order_id 订单号
     * @param float $total_amount 支付金额
     * @param string $subject 主题
     * @return string
     * @throws DefaultException
     * @throws \EasySwoole\Pay\Exceptions\InvalidConfigException
     */
    public function pay(int $payment_id, string $order_id, float $total_amount, string $subject, string $gate_way = null){
        $this->paymentConfig($payment_id, $gate_way);

// 商户号
        $merchantId = $this->partner_id;

// 从本地文件中加载「商户API私钥」，「商户API私钥」会用来生成请求的签名
        $merchantPrivateKeyFilePath = $this->private_key;
        $merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath, Rsa::KEY_TYPE_PRIVATE);

// 「商户API证书」的「证书序列号」
        $merchantCertificateSerial = $this->serial_no;

// 从本地文件中加载「微信支付平台证书」，用来验证微信支付应答的签名
        $platformCertificateFilePath = $this->public_key;
        $platformPublicKeyInstance = Rsa::from($platformCertificateFilePath, Rsa::KEY_TYPE_PUBLIC);

// 从「微信支付平台证书」中获取「证书序列号」
        $platformCertificateSerial = PemUtil::parseCertificateSerialNo($platformCertificateFilePath);

// 构造一个 APIv3 客户端实例
        $instance = Builder::factory([
            'mchid'      => $merchantId,
            'serial'     => $merchantCertificateSerial,
            'privateKey' => $merchantPrivateKeyInstance,
            'certs'      => [
                $platformCertificateSerial => $platformPublicKeyInstance,
            ],
        ]);
        try {
            $resp = $instance
                ->chain('v3/pay/transactions/app')
                ->post(['json' => [
                    'mchid'        => $merchantId,
                    'out_trade_no' => $order_id,
                    'appid'        => $this->app_id,
                    'description'  => $subject,
                    'notify_url'   => $this->notify_url,
                    'amount'       => [
                        'total'    => $total_amount,
                        'currency' => 'CNY'
                    ],
                ]]);
            $res = json_decode($resp->getBody(), true);
            if($res && isset($res['prepay_id'])) {

                $params = [
                    'appid'     => $this->app_id,
                    'timeStamp' => (string)Formatter::timestamp(),
                    'nonceStr'  => Formatter::nonce(),
                    'prepayid'  => $res['prepay_id']
                ];
                $params += [
                    'paySign'  => Rsa::sign(
                        Formatter::joinedByLineFeed(...array_values($params)),
                        $merchantPrivateKeyInstance
                    ),
                    'partnerid' => $merchantId,
                    'package' => 'Sign=WXPay',
                    'signType' => 'RSA'
                ];

                return $params;
            }else{
                throw new DefaultException('支付订单错误！');
            }
            return $res;
//            echo $resp->getStatusCode(), PHP_EOL;
//            echo $resp->getBody(), PHP_EOL;
        } catch (\Exception $e) {

            // 进行错误处理
//            echo $e->getMessage(), PHP_EOL;
            if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
                $r = $e->getResponse();
                throw new DefaultException($r->getStatusCode() . ' ' . $r->getReasonPhrase() . ' ' . $r->getBody());
                $r = $e->getResponse();
                echo $r->getStatusCode() . ' ' . $r->getReasonPhrase(), PHP_EOL;
                echo $r->getBody(), PHP_EOL, PHP_EOL, PHP_EOL;
            }
//            echo $e->getTraceAsString(), PHP_EOL;
        }



        // 设置支付配置
        $aliConfig = new \EasySwoole\Pay\AliPay\Config();
        ### 配置支付公共请求参数
        // (必须)设置 支付宝分配给开发者的应用ID
        $aliConfig->setAppId($this->app_id);
        // (必须)设置 请求网关(默认为 沙箱模式)
        $aliConfig->setGateWay($this->gate_way);

        // 设置 商户生成签名字符串所使用的签名算法类型，目前支持 RSA2 和 RSA，推荐使用 RSA2(默认为 'RSA2')
        $aliConfig->setSignType('RSA2');

        // 设置 支付宝服务器主动通知商户服务器里指定的页面http/https路径，即支付回调地址(默认为 null，可选参数)
        $aliConfig->setNotifyUrl($this->notify_url);

        // 设置 阿里应用公钥(支持 .pem 结尾的格式，默认为 PKCS1 格式)，用于支付回调时验证签名
        $aliConfig->setPublicKey($this->public_key); // 示例应用公钥字符串
        // 设置 阿里应用私钥(支持 .pem 结尾的格式，默认为 PKCS1 格式)，用于生成签名
        $aliConfig->setPrivateKey($this->private_key); // 示例应用私钥

        $pay = new \EasySwoole\Pay\Pay();

        ## (面向对象风格)设置请求参数 biz_content，组件自动帮你组装成对应的格式
        $order = new \EasySwoole\Pay\AliPay\RequestBean\App();
        // (必须)设置 订单总金额
        $order->setTotalAmount($total_amount); // 示例订单总金额，单位：元(仅供参考)
        // (必须)设置 商品标题/交易标题/订单标题/订单关键字等。注意：不可使用特殊字符，如 /，=，& 等。
        $order->setSubject($subject); // 示例商品标题(仅供参考)
        // (必须)设置 商户订单号(商户订单号。64 个字符以内的大小，仅支持字母、数字、下划线。需保证该参数在商户端不重复。)
        $order->setOutTradeNo($order_id); // 示例订单号(仅供参考)

        // 获取构造请求参数对象
        $res = $pay->aliPay($aliConfig)->app($order);
        // 拼接请求参数
        $aliUrlArr = [];
        foreach ($res->toArray() as $key => $val){
            $aliUrlArr[] = $key . "=" . urlencode($val);
        }
        return implode('&', $aliUrlArr);
    }

    public function verify(int $payment_id, array $data){
        $this->paymentConfig($payment_id);

        $apiv3Key = $this->api_key;// 在商户平台上设置的APIv3密钥
        $ciphertext = $data['resource']['ciphertext'];
        $nonce = $data['resource']['nonce'];
        $aad = $data['resource']['associated_data'];
        // 加密文本消息解密
        $inBodyResource = AesGcm::decrypt($ciphertext, $apiv3Key, $nonce, $aad);
        // 把解密后的文本转换为PHP Array数组
        $inBodyResourceArray = (array)json_decode($inBodyResource, true);

        return $inBodyResourceArray;
    }
}
